package vip.liux.front.infrastructure.config;

import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import lombok.Data;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.cache.SpringCacheBasedUserCache;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import vip.liux.contracts.security.AuthenticationManagerAware;
import vip.liux.contracts.security.sms.CaptchaCache;
import vip.liux.contracts.security.sms.SpringCacheBasedCaptchaCache;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

@Data
@Order
@Configuration
@ConfigurationProperties("ras")
@EnableWebSecurity(debug = true)
public class KeyConfiguration {

    private RSAPublicKey publicKey;

    private RSAPrivateKey privateKey;

    /**
     * JWT 解码器
     *
     * @return JWT 解码器
     */
    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(this.publicKey).build();
    }

    /**
     * JWT 编码器
     *
     * @return JWT 编码器
     */
    @Bean
    JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(this.publicKey).privateKey(this.privateKey).build();
        JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwkSource);
    }

    /**
     * 用户缓存
     *
     * @param cacheManager 缓存管理器
     * @return 用户缓存
     */
    @Bean
    public UserCache userCache(CacheManager cacheManager) {
        return new SpringCacheBasedUserCache(cacheManager.getCache("userCache"));
    }

    /**
     * 验证码缓存
     *
     * @param cacheManager 缓存管理器
     * @return 验证码缓存
     */
    @Bean
    public CaptchaCache captchaCache(CacheManager cacheManager) {
        return new SpringCacheBasedCaptchaCache(cacheManager.getCache("captchaCache"));
    }

    @Bean
    public SecurityFilterChain securityFilterChain(
            HttpSecurity http, ObjectProvider<AuthenticationManagerAware> authenticationManagerAware
    ) throws Exception {
        // @formatter:off
        http
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers(
                                "/api/account/authentication/token",
                                "/api/account/form/login",
                                "/api/account/register",
                                "/api/account/passport/**",
                                "/api/ai/chat/**",
                                "/api/captcha/**",
                                "/api/setting/**",
                                "/api/store/**",
                                "/swagger-ui.html",
                                "/swagger-ui/**",
                                "/v3/api-docs/**",
                                "/swagger-resources/**",
                                "/webjars/**"
                        ).permitAll()
                        .anyRequest().authenticated()
                )
                .csrf(AbstractHttpConfigurer::disable)
                .formLogin(formLoginConfigurer -> {
                    formLoginConfigurer.loginProcessingUrl("/api/account/form/login");
                    formLoginConfigurer.successForwardUrl("/api/account/authentication/token");
                })
                .oauth2ResourceServer(oAuth2ResourceServerConfigurer ->
                        oAuth2ResourceServerConfigurer.jwt(jwtConfigurer -> {}))
                .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling((exceptions) -> exceptions
                        .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                        .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
                );
        // @formatter:on

        // http.with(new SmsAuthenticationConfig<>(), smsLoginConfigurer -> {
        //     smsLoginConfigurer.loginProcessingUrl("/api/account/sms/login");
        //     smsLoginConfigurer.successForwardUrl("/api/account/authentication/token");
        // });

        DefaultSecurityFilterChain chain = http.build();

        AuthenticationManager manager = http.getSharedObject(AuthenticationManagerBuilder.class).getObject();
        authenticationManagerAware.stream().forEach(o -> o.setAuthenticationManager(manager));

        return chain;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
